<?php
namespace UserProposal;

use Pinenut\Model2\Adapter\Mongo;
use Pinenut\Model2\Finder;

/**
 * Предложения пользователей
 *
 * Используется для сохранения возможных действий над системой
 * Которые после их подтверждения другими пользователя реализуются
 * автоматически
 *
 * Используется для двухэтапной автомодерации содержимого сайта,
 * для просто модерации сайта, для верификации пользователей
 *
 * При создании предложения по типу определяются экшны, отвечающие за отрисовку
 * и обработку формы предложения и формы рассмотрения предложения
 * При решении подтвердить предложение - вызываем действие отвечающее за модификацию
 * (как вариант в очередь для отложенного выполнения)
 *
 * @author kosh
 *
 */
class Proposal extends \Model {

	/**
	 *
	 * @var \Pinenut\Config
	 */
	static $config = '\UserProposalConfig';

	/**
	 * Состояние - не готова, в данный момент предлоежние заполняется пользователем
	 * TODO: Нужно удалять если долго висит
	 */
	const STATE_CREATED = 0;

	/**
	 * Предложение заполнено, находится на рассмотрении
	 */
	const STATE_PROPOSED = 1;

	/**
	 * По предложению принято решение
	 */
	const STATE_JUDGED = 2;

	/**
	 * По предложению принято и исполнено решение
	 */
	const STATE_EXECUTED = 3;

	/**
	 * Состояние предложения
	 *
	 *
	 * @var int
	 */
	public $state;

	/**
	 * Время создания предложения
	 *
	 * @var int
	 */
	public $created;

	/**
	 * Время предложения (первого поступившего)
	 *
	 * @var int
	 */
	public $proposed;

	/**
	 * Время принятия решения
	 *
	 * @var int
	 */
	public $judged;

	/**
	 * Время выполнения решения
	 *
	 * @var int
	 */
	public $executed;

	/**
	 * тип действия в себе несет и сущности и действие (через конфу)
	 *
	 * @var string
	 */
	public $type;

	/**
	 * Идентификатор сущности, над которой производится действие
	 * По нему и типу идёт группировка счетов некоторых действий,
	 * поэтому вынесен из праметров
	 *
	 * @var string
	 */
	public $id;

	/**
	 * Массив параметров, в случае с переименованием например
	 * старое и новое значение и т.д.
	 *
	 * @var array
	 */
	public $params = array();

	/**
	 * Если пользователь авторизован используется идентификатор пользователя,
	 * иначе идентификатор сессии
	 *
	 * @var string
	 */
	public $userIds = [];

	/**
	 * Массив идентификаторов сессий/пользователей
	 *
	 * @var string[]
	 */
	public $surveysUserIds = [];

	/**
	 * Количество заданных вопросов
	 *
	 * @var integer
	 */
	public $surveysCount = 0;

	/**
	 * Массив идентификаторов сессий/пользователей, подтвердивших предложение
	 *
	 * @var string[]
	 */
	public $confirmedUserIds = [];

	/**
	 * Количество подтверждающих необходимость действия ответов
	 *
	 * @var integer
	 */
	public $confirmedCount = 0;

	/**
	 * Массив идентификаторов сессий/пользователей, отклонивших предложение
	 *
	 * @var string[]
	 */
	public $rejectedUserIds = [];

	/**
	 * Количество отвергающих ответов
	 *
	 * @var integer
	 */
	public $rejectedCount = 0;

	/**
	 * Принято решение
	 *
	 * @var bool
	 */
	public $resolution = false;

	static function fields() {
		return array_merge(parent::fields(),
			array(
				'type' => self::STRING,
				'id' => self::STRING,
				'state' => self::INT,
				'userIds' => self::MIXED,
				'created' => self::DATE,
				'proposed' => self::DATE,
				'judged' => self::DATE,
				'executed' => self::DATE,
				'params' => self::MIXED,
				'surveysUserIds' => self::MIXED,
				'surveysCount' => self::INT,
				'confirmedUserIds' => self::MIXED,
				'confirmedCount' => self::INT,
				'rejectedUserIds' => self::MIXED,
				'rejectedCount' => self::INT,
				'resolution' => self::BOOLEAN
			));
	}

	function getConfig( ) {
		$config = static::$config;
		return $config::get($this->type);
	}

	/**
	 * Получаем предложение или создаём новое
	 *
	 * @param string $userId
	 * @param string $type
	 * @param string $id
	 * @param boolean $create
	 * @return Ambigous <UserProposal, NULL, \Pinenut\Model2, mixed>
	 */
	public static function get($userId, $type, $id, $create = false) {

		// по типу определяем мульти-предложение или одиночное
		$config = static::$config;
		$typeConfig = $config::get($type);
		$multi = isset($typeConfig['multi']) ? ! ! $typeConfig['multi'] : false;
		$ungrouped = isset($typeConfig['ungrouped']) ? ! ! $typeConfig['ungrouped'] : false;

		$finder = static::find()->where('type', $type)->where('id', $id);

		if ($ungrouped) {
			// если негруппированная, то ищем только что созданную заявку
			$finder->where('state', [
				static::STATE_CREATED
			], Finder::OP_IN);
		} else {
			// иначе моежм предложения добавлять в уже предложенные заявки
			$finder->where('state',
				[
					static::STATE_CREATED,
					static::STATE_PROPOSED,
					static::STATE_JUDGED
				], Finder::OP_IN);
		}
		// мульти - несколько пользователей в одно предложение,
		// одиночный - группировка предложений только одного пользователя
		if (! $multi) {
			// Для одиночных предложений ищем запись именно этого пользователя
			// Соответственно для мульти-предложениия на всё равно кто там в
			// пользователях, главное чтоб был
			$finder->where('userIds', (string) $userId, Finder::OP_EQ);
		}

		$proposal = $finder->one();

		if (! $proposal && $create) {
			$proposal = new static(); // тут можно ыло бы не создават их,
			                          // а проверять вначале на уже существующие в базе
			$proposal->type = $type;
			$proposal->id = $id;
			$proposal->userIds[] = (string) $userId;
			$proposal->state = static::STATE_CREATED;
			$proposal->created = time();
			$proposal->save();
		}
		return $proposal;
	}

	function beforeSave( ) {
		// отслеживаем изменения статусов
		if (! $this->isPartial()) {

			// если очередное суждение - определяем не выполнилось ли условие
			// для принятия решения по предлжению

			if (static::STATE_PROPOSED == $this->state) {
				// достаём конфу, попределяем лимиты
				$config = $this->getConfig();
				// если набрали нужный процент, или стало очень много голосов - делаем решение
				if ($this->surveysCount >= $config['min'] && (100 *
					 $this->confirmedCount / $this->surveysCount >=
					 $config['percentage']) ||
					 $this->surveysCount >= $config['max']) {

					$this->state = static::STATE_JUDGED;
					$this->resolution = (100 * $this->confirmedCount /
						 $this->surveysCount >= $config['percentage']);
				}
			}

			if ($this->_raw['state'] != $this->state) {
				// если состояние изменилось не туда - ругаемся
				if ((int) $this->_raw['state'] > (int) $this->state) {
					throw new \Exception('Wrong state of user proposal', 500);
				}

				// если ок - устанавливаем время
				switch ($this->state) {
					case static::STATE_CREATED:
						$this->created = time();
						break;
					case static::STATE_PROPOSED:
						$this->proposed = time();
						break;
					case static::STATE_JUDGED:
						$this->judged = time();
						break;
					case static::STATE_EXECUTED:
						$this->executed = time();
						break;
				}
			}
		}
	}

	/**
	 * Делал ли данный пользователь предложение или еще нет
	 *
	 * @param unknown $userId
	 * @return boolean
	 */
	function isProposalUser($userId) {
		return in_array($userId, $this->userIds) &&
			 (static::STATE_PROPOSED <= $this->state);
	}

	/**
	 * Возвращаем очередное предложение, по которому пользователю можно вынести решение
	 *
	 * @param unknown $userId
	 */
	static function getForSurvey($userId, $type = null, $problemType = null) {

		// ->where('userIds', [
		// $userId
		// ], Finder::OP_NIN)
		$finder = static::find()->where('surveysUserIds', [
			$userId
		], Finder::OP_NIN)->where('state', static::STATE_PROPOSED);
		if (null !== $type) {
			$finder->where('type', (array) $type, Finder::OP_IN);
		}

		if (null !== $problemType) {
			$finder->where('params.problemType', $problemType);
		}

		return $finder->one();
	}

	static function getForSurveyCount($userId, $type = null, $problemType = null) {
		$finder = static::find()->where('surveysUserIds', [
			$userId
		], Finder::OP_NIN)->where('state', static::STATE_PROPOSED);

		if (null !== $type) {
			$finder->where('type', (array) $type, Finder::OP_IN);
		}

		if (null !== $problemType) {
			$finder->where('params.problemType', $problemType);
		}

		return $finder->all();
	}

	/**
	 *
	 * @param
	 *        	юзера айди $id
	 * @param
	 *        	массив типов. Должен быть именно массив. $type
	 *        	возвращаем самую последнюю заявку.
	 *
	 * $all		вернуть все предыдущие записи или только одну последнюю
	 */
	static function getExecuted($userId, $type, $all = false) {

		$finder = Proposal::find()
			//->where('id', $id) нихуя не так

			->where('userIds', $userId)
			->where('type', $type)
			->where('state', static::STATE_EXECUTED)
			->orderReverse('executed')
			->active(false);

		if ($all) {
			return $finder->all();
		} else {
			return $finder->one();
		}
	}
}